﻿using DotNetCore.CAP;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NewRetail.Application.Components.Platform.Entity;
using NewRetail.Application.Components.Warehouse.Entity;
using NewRetail.Application.Core;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Json;
using Volo.Abp.Uow;

namespace NewRetail.Application.Worker
{
    public class WarehouseGoodsCalMessage
    {
        public string warehouseId { get; set; }
        public string goodsId { get; set; }
    }

    public class WarehouseGoodsCal : MyApplicationService, ICapSubscribe, ITransientDependency
    {
        private readonly ILogger logger;
        private readonly IRepository<PosDaySum> posDaySums;
        private readonly IJsonSerializer jsonSerializer;
        private readonly INewRetailEventBus newRetailEventBus;
        private readonly IUnitOfWorkManager unitOfWorkManager;
        private readonly RedisClient redisClient;
        private readonly RedLockFactoryWrapper redLockFactoryWrapper;

        public WarehouseGoodsCal(ILogger<WarehouseGoodsCal> logger,
            IRepository<PosDaySum> posDaySums,
            IJsonSerializer jsonSerializer,
            RedisClient redisClient,
            RedLockFactoryWrapper redLockFactoryWrapper,
            IUnitOfWorkManager unitOfWorkManager,
            INewRetailEventBus newRetailEventBus)
        {
            this.logger = logger;
            this.jsonSerializer = jsonSerializer;
            this.posDaySums = posDaySums;
            this.redisClient = redisClient;
            this.redLockFactoryWrapper = redLockFactoryWrapper;
            this.newRetailEventBus = newRetailEventBus;
            this.unitOfWorkManager = unitOfWorkManager;
        }

        /// <summary>
        /// 计算当天的汇总，如果已经计算就覆盖
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [CapSubscribe(MessageTopics.WAREHOUSE_GOODS_COST_CAL)]
        [UnitOfWork]
        public virtual async Task OnMessage(WarehouseGoodsCalMessage data)
        {
            var startTime = this.Clock.Now;
            using var uow = this.unitOfWorkManager.Begin(requiresNew: true);

            using var redLock = await this.redLockFactoryWrapper.RedLockFactory.CreateLockAsync(
                resource: $"{nameof(WarehouseGoodsCal)}.{nameof(OnMessage)}.{data.warehouseId}.{data.goodsId}",
                expiryTime: TimeSpan.FromMinutes(10),
                waitTime: TimeSpan.FromMinutes(1),
                retryTime: TimeSpan.FromSeconds(3)
                );
            if (redLock.IsAcquired)
            {
                try
                {
                    var db = await this.posDaySums.GetDbContextAsync();

                    var warehouseGoods = await db.Set<WarehouseGoods>()
                        .FirstOrDefaultAsync(x => x.warehouseID == data.warehouseId && x.goodsID == data.goodsId);

                    warehouseGoods.Should().NotBeNull($"warehouse-goods:w:{data.warehouseId}-g:{data.goodsId}");

                    var query = db.Set<InOutWarehousing>().AsNoTracking()
                        .Where(x => x.isAuditing)
                        .Where(x => x.warehouseID == data.warehouseId && x.goodsID == data.goodsId);

                    var inType = (int)InOrOutEnum.IN;
                    var outType = (int)InOrOutEnum.OUT;

                    warehouseGoods.costPrice = await query?.AverageAsync(x => x.price);

                    warehouseGoods.costAmount = warehouseGoods.costPrice * warehouseGoods.goodsNum;

                    await db.SaveChangesAsync();

                    await uow.CompleteAsync();
                }
                catch (Exception e)
                {
                    this.logger.LogError(e, e.Message);

                    await uow.RollbackAsync();
                    throw;
                }
            }
            else
            {
                this.logger.LogError(message: "获取锁失败");
                throw new BusinessException("获取锁失败");
            }
        }
    }
}
